package com.example.myapplication.ui.upload

import android.util.Log
import androidx.lifecycle.*
import com.example.myapplication.App
import com.example.myapplication.DEFAULT_CHUNK_SIZE
import com.example.myapplication.net.bean.response.BaseResponse
import com.example.myapplication.net.bean.response.InsertFileBean
import com.example.myapplication.databaseThreadPool
import com.example.myapplication.exceptions.FileUploadTaskByteErrorException
import com.example.myapplication.exceptions.FileUploadTaskException
import com.example.myapplication.exceptions.FileUploadTaskNotExistException
import com.example.myapplication.exceptions.FileUploadTaskNotFoundException
import com.example.myapplication.extensions.md5
import com.example.myapplication.net.AbstractCallback
import com.example.myapplication.net.ServiceApi
import com.example.myapplication.net.bean.request.*
import com.example.myapplication.ui.FileBean
import com.example.myapplication.ui.FileBean.Companion.STATE_DEFAULT_FLAG
import com.example.myapplication.ui.FileBean.Companion.STATE_LOCAL_FLAG
import com.example.myapplication.ui.FileBean.Companion.STATE_NET_FLAG
import com.example.myapplication.ui.upload.FileUploadBean.Companion.UPLOAD_ERROR
import com.example.myapplication.ui.upload.FileUploadBean.Companion.UPLOAD_FINISH
import com.example.myapplication.ui.upload.FileUploadBean.Companion.UPLOAD_RUNNING
import okhttp3.MediaType
import okhttp3.MultipartBody
import java.io.File
import java.io.FileInputStream
import java.net.ConnectException
import java.nio.ByteBuffer
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicInteger
import kotlin.Exception

class FileUploadViewModel : ViewModel(), DefaultLifecycleObserver {

    private val dao: FileUploadDao = App.instance.database.fileUploadDao()

    private val remote = App.instance.apiNetwork.remote(ServiceApi::class.java)

    private val netThreadPool = Executors.newFixedThreadPool(3)

    private var timer: Timer = Timer()

    private val timerReUploadTask = object : TimerTask() {
        override fun run() {
            if (currTask() >= 2) return
            dao.getOtherNotUploadTask()?.also {
                Log.e("FileUploadViewModel", "Timer > ${it.name}")
                retry(it)
            }
        }
    }

    init {
        timer.schedule(timerReUploadTask, 0, 2000)
    }
    /**
     * 获取所有的任务
     */
    fun getAllFileByFlow() = dao.getAlphabetizedWordsByFlow().asLiveData()

    /**
     * 更新数据的字段
     */
    fun update(
        uuid: String,
        uploadSize: Long,
        uploadState: Byte,
        uploadSpeed: Long,
        state: String
    ) {
        databaseThreadPool.execute {
            dao.update(uuid, uploadSize, uploadState, uploadSpeed, state)
        }
    }

    // 任务重试
    fun retry(data: FileUploadBean) {
        // 检查是否存在 fileTask是否有上传ID
        if (data.uploadId == null || data.uploadId!!.isEmpty()) {
            if (data.enabled && !data.deleted) {
                requestFileUploadId(data)
            }
            return
        }
        // 进行任务上传
        netThreadPool.execute {
            val baseRequest = BaseRequest<EmptyParams>().apply {
                this.projectId = 565
                this.userId = App.instance.getUserId()
                this.token = App.instance.getToken()
            }
            readyUploadFile(remote, baseRequest, data)
        }
    }

    // 更新所有的任务，当关闭软件并再次打开时，刷新未完成任务的显示
    fun updateAllTaskState() {
        databaseThreadPool.execute {
            dao.updateAllTask()
        }
    }

    // 移除任务 指定UUID
    fun delete(file: FileBean) {
        databaseThreadPool.execute {
            dao.deleteByUUID(file.uuid)
        }
    }

    // 移除全部完成的任务
    fun deleteAll() {
        databaseThreadPool.execute {
            dao.deleteFinishAll()
        }
    }

    // 添加新任务，并进行上传操作
    fun insert(word: FileUploadBean) {
        databaseThreadPool.execute {
            dao.insert(word)
            if (word.enabled && !word.deleted) {
                requestFileUploadId(word)
            }
        }
    }

    // 更新任务的上传id
    fun update(uuid: String, uploadId: String) {
        databaseThreadPool.execute {
            dao.update(uuid, uploadId)
        }
    }

    // 移除正在上传的任务
    fun deleteRunningTask() {
        databaseThreadPool.execute {
            dao.deleteRunningAll()
        }
    }

    // 更新失败的任务
    fun updateErrorTask() {
        databaseThreadPool.execute {
            dao.updateErrorAll()
            // 获取一个任务进行上传
        }
    }

    // 移除错误的任务
    fun deleteErrorTask() {
        databaseThreadPool.execute {
            dao.deleteErrorAll()
        }
    }

    // 获取指定的UUid的任务 旨在判断任务是否存在
    private fun single(uuid: String): FileUploadBean? {
        return dao.getFileUploadBeanByUUID(uuid)
    }

    // 开始上传任务
    private fun readyUploadFile(
        remote: ServiceApi,
        baseRequest: BaseRequest<*>,
        word: FileUploadBean
    ) {
        // 判断任务是否已满
        addTask {
            Log.e("startUploadFile", "1")
            netThreadPool.execute {
                Log.e("startUploadFile", "2")
                val file: File
                try {
                    if (word.path == null) {
                        throw FileUploadTaskNotExistException("找不到源文件:路径为空")
                    }
                    file = File(word.path!!)
                    if (!file.exists()) {
                        throw FileUploadTaskNotExistException("找不到源文件:文件不存在")
                    }
                } catch (e: Exception) {
                    update(
                        word.uuid,
                        word.uploadSize,
                        UPLOAD_ERROR,
                        0,
                        "${STATE_LOCAL_FLAG}${e.localizedMessage}"
                    )
                    // 任务标志 -1
                    removeTask()
                    Log.e("startUploadFile", "21 $e")
                    return@execute
                }
                Log.e("startUploadFile", "3")
                val inputStream = FileInputStream(file)
                val channel = inputStream.channel
                val buffer = ByteBuffer.allocate(DEFAULT_CHUNK_SIZE)
                var uploadSize = word.uploadSize
                try {
                    var readSize: Int
                    val fileLength = file.length()
                    channel.position(uploadSize)
                    update(
                        word.uuid,
                        uploadSize,
                        UPLOAD_RUNNING, 0,
                        "${STATE_DEFAULT_FLAG}已上传:${(uploadSize.toDouble() / fileLength * 100).toInt()}%"
                    )
                    var startTime = System.currentTimeMillis()
                    Log.e("startUploadFile", "4")
                    while (channel.read(buffer).also { readSize = it } > 0) {
                        Log.e("startUploadFile", "41  $readSize")
                        val blockArray = ByteArray(readSize)
                        buffer.flip()
                        buffer.get(blockArray)
                        val checkParam = CheckChunkExistParams(
                            docName = file.name,
                            docSize = fileLength,
                            docChunkNumber = Math.ceil(fileLength.toDouble() / DEFAULT_CHUNK_SIZE).toInt(),
                            index = (if (uploadSize % DEFAULT_CHUNK_SIZE == 0L) uploadSize / DEFAULT_CHUNK_SIZE else (uploadSize / DEFAULT_CHUNK_SIZE) + 1).toInt(),
                            blockSize = DEFAULT_CHUNK_SIZE,
                            chunkSize = readSize,
                            uploadId = word.uploadId!!,
                            hashCode = blockArray.md5().toLowerCase(Locale.getDefault())
                        )
                        val uploadCheck =
                            remote.checkChunkExist(BaseRequest<CheckChunkExistParams>(baseRequest).apply {
                                this.data = checkParam
                            }).execute()
                        if (uploadCheck.isSuccessful) {
                            val body = uploadCheck.body()
                            if (body.data == false) {
                                val execute = remote.chunkUpload(
                                    hashCode = checkParam.hashCode,
                                    index = checkParam.index,
                                    docChunkNumber = checkParam.docChunkNumber,
                                    uploadId = checkParam.uploadId,
                                    docSize = checkParam.docSize,
                                    chunkSize = checkParam.chunkSize,
                                    blockSize = checkParam.blockSize,
                                    docName = checkParam.docName,
                                    token = App.instance.getToken()!!,
                                    userId = App.instance.getUserId(),
                                    file = MultipartBody.Part.createFormData(
                                        "file",
                                        checkParam.docName,
                                        MultipartBody.create(
                                            MediaType.parse("multipart/form-data"),
                                            blockArray
                                        )
                                    )
                                ).execute()
                                execute.also {
                                    uploadSize += readSize
                                }
                            } else {
                                uploadSize += readSize
                            }
                            val currTime = System.currentTimeMillis()
                            val spaceSecond = (currTime - startTime).toDouble() / 1000
                            val uploadByteSize = readSize / 1024
                            val speed = uploadByteSize / spaceSecond
                            startTime = currTime
                            single(word.uuid)?.also {
                                update(
                                    it.uuid, uploadSize, UPLOAD_RUNNING, speed.toLong(),
                                    "${STATE_DEFAULT_FLAG}已上传:${(uploadSize.toDouble() / fileLength * 100).toInt()}%"
                                )
                            } ?: let {
                                throw FileUploadTaskNotFoundException()
                            }
                        }
                        buffer.rewind()
                    }
                    Log.e("startUploadFile", "5")
                    if (uploadSize != fileLength) {
                        throw FileUploadTaskByteErrorException("上传字节不等于文件字节长度")
                    }
                    Log.e("startUploadFile", "6")
                    val insertFileParams = InsertFileParams(
                        docSize = fileLength,
                        docName = file.name,
                        docHashcode = word.uploadId!!,
                        docVersionRfs = word.uploadId!!,
                        createBy = App.instance.getUserId(),
                        updateBy = App.instance.getUserId(),
                        projId = 565,
                        docSource = 0,
                        parentId = 412069,
                        docDescript = "上传新文件"
                    )
                    Log.e("startUploadFile", "7")

                    val insertFileRequest = BaseRequest<InsertFileParams>(baseRequest).apply {
                        this.data = insertFileParams
                    }
                    Log.e("startUploadFile", "8")

                    remote.insertFile(insertFileRequest)
                        .enqueue(object :
                            AbstractCallback<BaseResponse<InsertFileBean>>("app/rfs/insert") {
                            override fun onSuccess(t: BaseResponse<InsertFileBean>) {
                                val data = t.data ?: return
                                val insertFileInDirParams = InsertFileInDirParams(
                                    projectId = insertFileParams.projId,
                                    docId = data.docId,
                                    docType = "0",
                                    docName = data.docName!!,
                                    rfsId = data.rfsId!!,
                                    auditProjectId = "1659",
                                    parentId = 412069
                                )
                                remote.insertDirFile(BaseRequest<InsertFileInDirParams>(baseRequest).apply {
                                    this.data = insertFileInDirParams
                                }).enqueue(object :
                                    AbstractCallback<BaseResponse<Any>>("app/project/doc/insert") {
                                    override fun onSuccess(t: BaseResponse<Any>) {
                                        removeTask()
                                        update(
                                            word.uuid, uploadSize, UPLOAD_FINISH, 0,
                                            "${STATE_DEFAULT_FLAG}上传到:${data.docName!!}"
                                        )
                                    }
                                })
                            }
                        })
                } catch (e: Throwable) {
                    Log.e("startUploadFile", "10  ${e.localizedMessage} \n $e")
                    e.printStackTrace()
                    removeTask()
                    if (e !is FileUploadTaskNotFoundException) {
                        single(word.uuid)?.also {
                            update(
                                it.uuid, it.uploadSize, UPLOAD_ERROR, 0,
                                "${STATE_NET_FLAG}网络异常，请点击重新上传"
                            )
                        }
                    }
                } finally {
                    channel.close()
                    inputStream.close()
                }
            }
        }
    }

    // 获取任务的上传ID
    private fun requestFileUploadId(word: FileUploadBean) {
        netThreadPool.execute(object : Runnable {
            override fun run() {
                try {
                    val baseRequest = BaseRequest<EmptyParams>().apply {
                        this.projectId = 565
                        this.userId = App.instance.getUserId()
                        this.token = App.instance.getToken()
                    }
                    val execute = remote.initUpload(baseRequest).execute()
                    if (execute == null || !execute.isSuccessful) {
                        throw FileUploadTaskException("获取文件上传ID请求失败")
                    }
                    val data = execute.body()?.data ?: throw FileUploadTaskException("获取文件上传ID为空")
                    word.apply {
                        this.uploadId = data
                        update(word.uuid, data)// 更新上传任务Id
                    }
                    readyUploadFile(remote, baseRequest, word)
                } catch (e: Exception) {
                    Log.e("FileUploadViewModel", "requestFileUploadId ${e.localizedMessage}")
                    update(
                        word.uuid,
                        word.uploadSize,
                        UPLOAD_ERROR,
                        0,
                        "${STATE_NET_FLAG}网络异常，请点击重新上传"
                    )
                }
            }
        })
    }

    private val uploadTaskSize = AtomicInteger(0)

    private fun addTask(run: () -> Unit) {
        // 判断任务是否已满
        val addAndGet = uploadTaskSize.get()
        if (addAndGet >= 2) {
            return
        }
        // 任务标志+1
        uploadTaskSize.getAndIncrement()
        run.invoke()
    }

    private fun currTask(): Int {
        return uploadTaskSize.get()
    }

    private fun removeTask() {
        uploadTaskSize.getAndDecrement()
        if (uploadTaskSize.get() < 0) {
            uploadTaskSize.set(0)
        }
    }
}